package main

import (
	"errors"
	"fmt"
	"math"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"logger"
	"panda"

	"github.com/xuri/excelize/v2"
)

var log = logger.Log

// AddMatrices 函数接收一个可变数量的浮点数二维切片作为参数，这些切片代表需要相加的矩阵。
// 该函数计算并返回这些矩阵的和，以及一个表示操作是否成功的错误值。
//
// 参数：
//
//	matrices ...[][]float64: 可变数量的浮点数二维切片，每个切片代表一个矩阵。
//
// 返回值：
//
//	[][]float64: 计算得到的矩阵和，类型为浮点数二维切片。
//	error: 表示操作是否成功的错误值。如果发生错误，将返回一个非nil的错误值。
func AddMatrices(matrices ...[][]float64) ([][]float64, error) {
	if len(matrices) == 0 {
		return nil, errors.New("至少需要传入一个矩阵")
	} else if len(matrices) == 1 || (len(matrices) == 2 && len(matrices[1]) == 0) {
		return matrices[0], nil
	}

	// 获取基准矩阵维度
	rows := len(matrices[0])
	cols := len(matrices[0][0])
	// 校验所有矩阵维度
	for _, m := range matrices {
		if len(m) == 0 {
			continue
		} else if len(m) != rows || (len(m) > 0 && len(m[0]) != cols) {
			return nil, errors.New("矩阵维度不一致")
		}
	}
	// 初始化结果矩阵
	result := make([][]float64, rows)
	for i := range result {
		result[i] = make([]float64, cols)
	}
	// 累加所有矩阵元素
	for _, m := range matrices {
		for i := range rows {
			for j := range cols {
				result[i][j] = math.Round((result[i][j]+m[i][j])*100) / 100
			}
		}
	}
	return result, nil
}

// ReadFileData 从指定路径的Excel文件中读取指定工作表和区域内的数据，并返回二维浮点数切片
//
// filePath: Excel文件的路径
// sheetName: Excel文件中工作表的名称
// region: 需要读取的单元格区域，格式为"A1:C3"
//
// 返回值:
// - data: 读取的数据，类型为二维浮点数切片
func ReadFileData(filePath string, sheetName string, region string) (data [][]float64, str string) {

	start := time.Now()
	defer func() {
		cost := time.Since(start)
		log.Debug("Read data from ", filepath.Base(filePath), " [", sheetName, "] (", region, ") cost:", cost)
	}()

	f, err := excelize.OpenFile(filePath)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	if !strings.Contains(region, ":") {
		value, _ := f.GetCellValue(sheetName, region)
		log.Debug(region, " -> ", value)
		return [][]float64{}, fmt.Sprint(value)
	}

	x1, y1, x2, y2 := CharToCell(region)

	// startRow, endRow := 6, 118 // 行索引（0-based）
	// startCol, endCol := 2, 8   // 列索引（0-based）

	for i := y1; i <= y2; i++ {
		var coldata []float64
		for j := x1; j <= x2; j++ {
			xy, _ := excelize.CoordinatesToCellName(j, i)  //列行索引转字符单元格
			opts := excelize.Options{RawCellValue: true}   //取原始数据而非显示值
			cell, _ := f.GetCellValue(sheetName, xy, opts) //获取单元格的数值，例如"Sheet1!A1"
			val, err := strconv.ParseFloat(strings.TrimSpace(cell), 64)
			if err != nil {
				val = 0
			}

			// Todo
			formula, err := f.GetCellFormula(sheetName, xy) // 检查单元格是否有公式

			if err != nil {
				println(err.Error())
			}
			lockstyle, _ := checkCellLock(f, sheetName, xy)
			if !lockstyle && strings.Contains(formula, "[") {
				// _log.Warn("请关注单元格：", sheetName, xy, "=", formula)
			}

			coldata = append(coldata, val)
		}
		data = append(data, coldata)
	}
	return data, ""
}

func checkCellLock(f *excelize.File, sheet, cell string) (bool, error) {
	// 获取单元格样式ID
	styleID, err := f.GetCellStyle(sheet, cell)
	if err != nil {
		return false, err
	}

	// 获取样式属性
	style, err := f.GetStyle(styleID)
	if err != nil {
		return false, err
	}

	// 返回锁定状态（默认新建单元格为锁定状态）
	return style.Protection == nil || style.Protection.Locked, nil
}

// CharToCell 将字符串格式的单元格坐标转换为整数格式的行列坐标
//
// origin：字符串格式的单元格坐标，格式为"单元格1:单元格2"
// x1,y1,x2,y2：整数格式的行列坐标，分别对应单元格1和单元格2的列和行
func CharToCell(origin string) (x1, y1, x2, y2 int) {
	cells := strings.Split(origin, ":")
	cell1 := cells[0]
	cell2 := cells[1]
	x1, y1, _ = excelize.CellNameToCoordinates(cell1)
	x2, y2, _ = excelize.CellNameToCoordinates(cell2)
	// log.Debug("\n", origin, "\t\tscol:", x1, " row:", y1, "\tcol:", x2, " row:", y2)
	return x1, y1, x2, y2
}

type SheetData struct {
	Name   string
	Region string
	Data   [][]float64
}

// WriteFileData 向指定路径的Excel文件中写入数据
// filePath: 文件路径
// sheetName: 工作表名称
// region: 区域范围，格式为"A1:C3"
// data: 要写入的数据，二维浮点数数组
// 返回值: 若有错误则返回error，否则返回nil
func WriteFileData(moudlePath, saveName string, datas ...SheetData) error {
	log.Debug("打开基础模板:", moudlePath, "\\合并预算报表.xlsx")
	f, err := excelize.OpenFile(moudlePath + "\\合并预算报表.xlsx")
	if err != nil {
		log.ColorRed(err.Error())
		return err
	}
	defer f.Close()
	// _log.ColorCyan("写入数据中...")
	for _, data := range datas {
		x1, y1, _, _ := CharToCell(data.Region)
		for i, rowData := range data.Data {
			for j, cellValue := range rowData {
				xy, _ := excelize.CoordinatesToCellName(j+x1, i+y1) // 根据需要调整起始列和行号
				// fmt.Println(j+x1, i+y1, xy)                         // 根据需要调整起始列和行号
				if cellValue == 0 {
					continue
				}

				tt, _ := checkCellLock(f, data.Name, xy)
				// log.ColorRed("写入单元格:", data.Name, xy, "=", cellValue, "锁定状态:", tt)
				if tt {
					continue
				}
				f.SetCellValue(data.Name, xy, cellValue)
			}
		}
	}
	f.SetActiveSheet(0)
	if err = f.UpdateLinkedValue(); err != nil {
		log.Warn("公式重算失败:", err)
	}
	err = f.SaveAs(saveName)
	if err != nil {
		log.Error("保存文件失败!请关闭文件后重试!")
		return err
	}
	log.Info("保存文件成功！", "详见目录下文件：", saveName)
	return nil
}

const (
	SHEET_FEES   = "期间费用预算"
	SHEET_PROFIT = "利润表预算"
	SHEET_CASH   = "现金流量预算"
	SHEET_BAL    = "资产负债表预算"
	SHEET_FIXED  = "固定资产购置预算"
)

// MergeFiles 合并多个Excel文件指定工作表区域的数据
//
// files: Excel文件路径列表
// sheetName: 工作表名称
// region: 数据区域（如"A1:C10"）
// SheetData: 包含合并后数据、工作表名称和区域的结构体
func MergeFiles(files []string, sheetName, region string) SheetData {

	var result [][]float64
	for _, file := range files {
		data, _ := ReadFileData(file, sheetName, region)
		result, _ = AddMatrices(data, result)
		if sheetName == SHEET_FIXED {
			// This is for debug, see what data is read
			log.Debug(file)
			// print_matrix(data)
		}
	}
	log.Info(sheetName, " data is merged->", region)
	if sheetName == SHEET_FIXED {
		// This is for debug, see result
		// log.ColorMagenta(result)...
	}
	return SheetData{Name: sheetName, Region: region, Data: result}
}

func print_matrix(matrix [][]float64) {
	for _, row := range matrix {
		for _, val := range row {
			log.Printf("%10.2f", val)
		}
		log.Println("")
	}
}

func flag_start() {
	log.Info(separator("程序开始执行", "=", 30, 0))
	log.Info("检测系统时间-> ", time.Now().Format("2006-01-02 15:04:05"))
}

func flag_end(start time.Time) {
	cost := time.Since(start)
	log.Info(separator("程序执行完毕!", "=", 30, 0))
	log.Warn("Program completed in 5 seconds:", cost, "\n", "\n")
}

func check_input_path() string {
	var path string
	fmt.Println("请输入[文件夹]路径,该路径下必须存在[合并预算报表.xlsx文件]")
	fmt.Println(`例如:C:\Users\64631\Desktop\财务预算,直接Enter使用运行路径`)
	fmt.Printf(">>>")
	fmt.Scanln(&path)

	if strings.TrimSpace(path) == "" {
		path, _ = os.Getwd()
		log.Warn("未输入路径，使用当前路径:", path)
		return path
	} else {
		info, err := os.Stat(path)
		if err != nil || !info.IsDir() {
			log.Error("输入路径无效，请检查后重新运行程序!")
			return ""
		}
	}
	return path
}

var Config = map[string]string{
	SHEET_FEES:   "B5:J120",
	SHEET_PROFIT: "B5:Q49",
	SHEET_CASH:   "B5:J41",
	SHEET_BAL:    "B5:N47",
	SHEET_FIXED:  "B5:E12",
}

// budget_merge 合并预算数据的主函数
func budget_merge() {

	flag_start()
	path := check_input_path()
	// ---------------------------------------------------------------------
	start := time.Now()
	defer func() {
		flag_end(start)
	}()
	// ---------------------------------------------------------------------

	// log.SetLevel(_log.LEVEL_DEBUG)

	files := find_file_in_path(path, "[^~合]*.xls*")

	log.Warn("符合条件的文件数量:", len(files))
	if len(files) == 0 {
		return
	}

	data_fees := MergeFiles(files, SHEET_FEES, Config[SHEET_FEES])
	data_profit := MergeFiles(files, SHEET_PROFIT, Config[SHEET_PROFIT])
	data_cash := MergeFiles(files, SHEET_CASH, Config[SHEET_CASH])
	data_bal := MergeFiles(files, SHEET_BAL, Config[SHEET_BAL])
	data_fixed := MergeFiles(files, SHEET_FIXED, Config[SHEET_FIXED])

	WriteFileData(path, "合并预算报表-生成版-请补充集团名称.xlsx", data_fees, data_profit, data_cash, data_bal, data_fixed)
}

// separator 函数用于生成带有分隔符的字符串
func separator(msg, sep string, length int, aligon int) string {
	str := strings.Repeat(sep, length)
	if aligon == 0 {
		return str + msg + str
	} else if aligon > 0 {
		return msg + str
	} else {
		return str + msg
	}
}

// budget_init 程序初始化函数
func budget_init() {
	flag_start()
	// ---------------------------------------------------------------------
	start := time.Now()
	defer func() {
		flag_end(start)
	}()
	// ---------------------------------------------------------------------

	path, _ := os.Getwd()
	log.Info("检测运行路径-> ", path)
	_, err := os.Stat(path + "/合并预算报表.xlsx")
	if os.IsNotExist(err) {
		log.Warn("模板文件缺失-> 未找到文件:合并预算报表.xlsx，如果想在后续功能中设置运行文件夹，请无视该提示！")
	} else {
		_, version := ReadFileData(path+"/合并预算报表.xlsx", SHEET_FEES, "F2")
		log.Info("模版文件版本-> ", version)
		_, year := ReadFileData(path+"/合并预算报表.xlsx", SHEET_FEES, "M2")
		check_expect(strings.TrimSpace(year), "2,026", "模板预算年度", "模板年度异常")
	}
}

func check_expect(result any, expect any, ok string, fail string) {
	if result == expect {
		log.Info(ok, "-> ", result)
	} else {
		log.Warn(fail, "-> 应为:", expect, "，实际:", result)
	}
}

func find_file_in_path(path string, pattern string) []string {

	files, err := filepath.Glob(path + "/" + pattern)
	if err != nil {
		log.Error("获取当前目录下xlsx文件失败!")
		return []string{}
	}
	log.Info("获取当前目录下符合条件的文件: \n----", strings.Join(files, "\n----"))
	return files
}

func budget_new() {
	flag_start()
	// ---------------------------------------------------------------------
	path := check_input_path()
	start := time.Now()
	defer func() {
		flag_end(start)
	}()
	// ---------------------------------------------------------------------
	files := find_file_in_path(path, "[^~合]*.xlsx")
	log.Debug(files)

	for _, file := range files {
		// data := ChangeFile(file, sheetname, region)

		data_fees := ChangeFile(file, SHEET_FEES, Config[SHEET_FEES])
		data_profit := ChangeFile(file, SHEET_PROFIT, Config[SHEET_PROFIT])
		data_cash := ChangeFile(file, SHEET_CASH, Config[SHEET_CASH])
		data_bal := ChangeFile(file, SHEET_BAL, Config[SHEET_BAL])
		data_fixed := ChangeFile(file, SHEET_FIXED, Config[SHEET_FIXED])
		WriteFileData(path, "合并预算报表-生成版-2026"+filepath.Base(file), data_fees, data_profit, data_cash, data_bal, data_fixed)
	}
	//This is for testing change_matrix function
	// olddata := [][]float64{
	// 	{854.98, 586.97, 457.9, 514.94, 0.8892, 714.36, 96.7},
	// 	{14211.7, 14668.98, 14407.37, 17549.67, 0.8209, 14502.61, 1236.67},
	// }
	// log.Debug(olddata)
	// newdata := change_matrix(olddata, SHEET_FEES)
	// log.Debug(newdata)
}

// ChangeFile 读取指定文件的指定工作表和区域的数据，并对数据进行列变换
func ChangeFile(filePath string, sheetName string, region string) SheetData {
	data, _ := ReadFileData(filePath, sheetName, region)
	// log.Info("原始数据:", data)
	changedData := change_matrix(data, sheetName)
	return SheetData{Name: sheetName, Region: region, Data: changedData}
}

// change_matrix 根据工作表名称对矩阵进行列变换
func change_matrix(matrix [][]float64, sheetName string) [][]float64 {

	Config := map[string]map[int]int{
		SHEET_FEES:   {1: 2, 2: 3, 4: 8},               //"1=2,2=3,4=7,7=0",
		SHEET_PROFIT: {2: 5, 3: 6, 5: 8, 6: 9, 11: 15}, //"2=5,3=6,5=8,6=9,11=15,14=0",
		SHEET_CASH:   {1: 2, 2: 3, 4: 8},               //type"1=2,2=3,4=7,7=0", // SHEET_BAL:    "B5:N47",
		SHEET_FIXED:  {1: 4},
	}

	// 获取基准矩阵维度
	rows := len(matrix)
	cols := len(matrix[0])

	// 初始化结果矩阵
	result := make([][]float64, rows)
	for i := range result {
		result[i] = make([]float64, cols)
	}

	rules := Config[sheetName]

	// for i := 0; i < rows; i++ {
	for new, old := range rules {
		log.Debug(sheetName, "(变列): To ", new, " From ", old)
		for raw := 0; raw < rows; raw++ {
			if old == 0 {
				matrix[raw][new-1] = 0
			} else {
				result[raw][new-1] = matrix[raw][old-1]
			}
		}
	}
	return result
}

// 主程序入口
func main() {
	log.SetPrefix(false)
	log.SetLevel(logger.LEVEL_DEBUG)

	for {
		input := panda.Input(
			`请输入指令选项,如：1
1.数据填报检查		2.生成下年预算		3.合并预算数据
7.切换调试模式		8.帮助信息查询		9.安全退出程序
>>>`)

		cmd_clean()
		budget_init()
		if input == "1" {
			log.Warn("数据填报检查功能正在开发中，敬请期待！")

		} else if input == "2" {
			log.Warn("生成下年预算功能初步完成，欢迎试用，有问题请反馈646318717@qq.com")
			budget_new()
		} else if input == "3" {
			budget_merge()
		} else if input == "7" {
			if log.Level == 0 {
				log.SetLevel(logger.LEVEL_INFO)
				log.Warn("如果你能看到该条信息，说明已经进入Info模式：调试信息以下不可见！")
			} else {
				log.SetLevel(logger.LEVEL_DEBUG)
				log.Warn("如果你能看到该条信息，说明已经进入Debug模式：调试信息以下可见！")
			}
		} else if input == "8" {
			log.Info(`1.数据填报检查：检查填报的预算数据是否符合要求。
2.生成下年预算：根据当前预算数据生成下一年度的预算模板。
3.合并预算数据：将多个预算文件的数据进行合并，生成合并预算报表。
7.切换调试模式：在调试模式和普通模式之间切换，以便查看或隐藏调试信息。
8.帮助信息查询：显示程序的使用说明和各项功能介绍。
9.安全退出程序：退出当前程序，确保数据保存和资源释放。`, "\n")
		} else if input == "9" || strings.Contains(input, "go run") {
			break
		}
	}
}

func cmd_clean() error {
	cmd := exec.Command("cmd", "/c", "cls")
	cmd.Stdout = os.Stdout
	return cmd.Run()
}
